package com.irain.pp.tool;

import java.security.SecureRandom;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.SecretKeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;

/**
 * @author WangYanPeng
 * @date 2017/11/1 15:13
 */
public enum HashUtils {
    /**
     * cookie
     */
    COOKIE("PBKDF2WithHmacSHA256", 20, 24, 20),
    /**
     * 版本1
     */
    V01("PBKDF2WithHmacSHA256", 25, 23, 1024),;
    private String secretKey;
    private int saltByteSize;
    private int hashByteSize;
    private int iterations;

    HashUtils(String secretKey, int saltByteSize, int hashByteSize, int iterations) {
        this.secretKey = secretKey;
        this.saltByteSize = saltByteSize;
        this.hashByteSize = hashByteSize;
        this.iterations = iterations;
    }

    public String createHashWithVersion(String plaintext) {
        return this.name() + ":" + createHash(plaintext);
    }

    public String createHash(String plaintext) {
        try {
            char[] plain = plaintext.toCharArray();
            SecureRandom random = new SecureRandom();
            byte[] salt = new byte[this.saltByteSize];
            random.nextBytes(salt);
            byte[] hash = secret(this.secretKey, plain, salt, this.iterations, this.hashByteSize);
            return ByteUtils.toHex(salt) + ByteUtils.toHex(hash);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw ExceptionUtils.transfer(e);
        }
    }

    public boolean validate(String plaintext, String correctHash)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] bytes = ByteUtils.fromHex(correctHash);
        char[] plain = plaintext.toCharArray();
        if (plain.length != bytes.length) return false;
        byte[] salt = Arrays.copyOfRange(bytes, 0, this.saltByteSize);
        byte[] hash = Arrays.copyOfRange(bytes, this.saltByteSize, bytes.length);
        byte[] testHash = secret(this.secretKey, plain, salt, this.iterations, hash.length);
        return slowEquals(hash, testHash);
    }

    public static boolean validateWithVersion(String plaintext, String correctHash) {
        try {
            String[] params = correctHash.split(":");
            if (params.length == 2) {
                HashUtils version = HashUtils.valueOf(params[0]);
                return version.validate(plaintext, params[1]);
            }
            return false;
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw ExceptionUtils.transfer(e);
        }
    }
    

    private static boolean slowEquals(byte[] a, byte[] b) {
        int diff = a.length ^ b.length;
        for (int i = 0; i < a.length && i < b.length; i++)
            diff |= a[i] ^ b[i];
        return diff == 0;
    }

    private static byte[] secret(String secretKey, char[] password, byte[] salt, int iterations, int bytes)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, bytes * 8);
        SecretKeyFactory skf = SecretKeyFactory.getInstance(secretKey);
        return skf.generateSecret(spec).getEncoded();
    }
}
